// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package os

import (
	"runtime"
	"sync"
	"syscall"
)

var getwdCache struct {
	sync.Mutex
	dir string
}

// Getwd返回当前目录对应的根路径名。如果当前目录可以是
// 通过多个路径到达（由于符号链接），则
// Getwd可以返回其中任何一个路径。
func Getwd() (dir string, err error) {
	if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
		return syscall.Getwd()
	}

	// 笨拙但广泛的混乱：
	// 如果设置了$PWD并匹配“，使用它。
	dot, err := statNolog(".")
	if err != nil {
		return "", err
	}
	dir = Getenv("PWD")
	if len(dir) > 0 && dir[0] == '/' {
		d, err := statNolog(dir)
		if err == nil && SameFile(dot, d) {
			return dir, nil
		}
	}

	// 如果操作系统提供Getwd调用，请使用它。
	// 否则，我们正试图找到回到“”的路。
	if syscall.ImplementsGetwd {
		var (
			s string
			e error
		)
		for {
			s, e = syscall.Getwd()
			if e != syscall.EINTR {
				break
			}
		}
		return s, NewSyscallError("getwd", e)
	}

	// 应用相同的kludge，但应用于缓存的dir，而不是$PWD。
	getwdCache.Lock()
	dir = getwdCache.dir
	getwdCache.Unlock()
	if len(dir) > 0 {
		d, err := statNolog(dir)
		if err == nil && SameFile(dot, d) {
			return dir, nil
		}
	}

	// Root是一个特例，因为它没有父
	// 并以斜杠结尾。
	root, err := statNolog("/")
	if err != nil {
		// 无法统计根-没有希望。
		return "", err
	}
	if SameFile(root, dot) {
		return "/", nil
	}

	// 通用算法：在父级中查找名称
	// 然后查找父级的名称。每次迭代
	// 都会在dir的开头添加/name。
	dir = ""
	for parent := ".."; ; parent = "../" + parent {
		if len(parent) >= 1024 { // 精神检查
			return "", syscall.ENAMETOOLONG
		}
		fd, err := openFileNolog(parent, O_RDONLY, 0)
		if err != nil {
			return "", err
		}

		for {
			names, err := fd.Readdirnames(100)
			if err != nil {
				fd.Close()
				return "", err
			}
			for _, name := range names {
				d, _ := lstatNolog(parent + "/" + name)
				if SameFile(d, dot) {
					dir = "/" + name + dir
					goto Found
				}
			}
		}

	Found:
		pd, err := fd.Stat()
		fd.Close()
		if err != nil {
			return "", err
		}
		if SameFile(pd, root) {
			break
		}
		// 为下一轮做好准备。
		dot = pd
	}

	// 将答案另存为提示，以避免下次使用昂贵的路径。
	getwdCache.Lock()
	getwdCache.dir = dir
	getwdCache.Unlock()

	return dir, nil
}
